What is import-in-the-middle?
The 'import-in-the-middle' npm package allows developers to intercept and modify module imports in Node.js. This can be useful for debugging, logging, or altering the behavior of modules without modifying their source code.
What are import-in-the-middle's main functionalities?
Intercepting Module Imports
This feature allows you to intercept the import of specified modules ('fs' and 'path' in this case) and execute custom logic (logging in this example) whenever these modules are imported.
const { addHook } = require('import-in-the-middle');
addHook(['fs', 'path'], (exports, name, baseDir) => {
console.log(`Module ${name} is being imported from ${baseDir}`);
return exports;
});
const fs = require('fs');
const path = require('path');
Modifying Module Exports
This feature allows you to modify the exports of a module. In this example, a custom method is added to the 'fs' module.
const { addHook } = require('import-in-the-middle');
addHook(['fs'], (exports, name, baseDir) => {
if (name === 'fs') {
exports.customMethod = () => console.log('Custom method added to fs');
}
return exports;
});
const fs = require('fs');
fs.customMethod(); // Outputs: Custom method added to fs
Other packages similar to import-in-the-middle
require-in-the-middle
The 'require-in-the-middle' package provides similar functionality for intercepting and modifying module imports, but it is specifically designed for CommonJS modules using 'require'. It does not support ES modules.
proxyquire
The 'proxyquire' package allows you to override dependencies during testing. It is more focused on providing mock implementations for dependencies, whereas 'import-in-the-middle' is more general-purpose for intercepting and modifying imports.
mock-require
The 'mock-require' package is used to mock Node.js modules during testing. It allows you to replace modules with mock implementations, similar to 'proxyquire', but it does not provide the same level of interception and modification capabilities as 'import-in-the-middle'.
import-in-the-middle
import-in-the-middle
is an module loading interceptor inspired by
require-in-the-middle
, but
specifically for ESM modules. In fact, it can even modify modules after loading
time.
Usage
The API for
require-in-the-middle
is followed as closely as possible as the default
export. There are lower-level addHook
and removeHook
exports available which
don't do any filtering of modules, and present the full file URL as a parameter
to the hook. See the Typescript definition file for detailed API docs.
You can modify anything exported from any given ESM or CJS module that's
imported in ESM files, regardless of whether they're imported statically or
dynamically.
import { Hook } from 'import-in-the-middle'
import { foo } from 'package-i-want-to-modify'
console.log(foo)
Hook(['package-i-want-to-modify'], (exported, name, baseDir) => {
exported.foo += 1
})
console.log(foo)
This requires the use of an ESM loader hook, which can be added with the following
command-line option.
node --loader=import-in-the-middle/hook.mjs my-app.mjs
Since --loader
has been deprecated you can also register the loader hook programmatically via the Node
module.register()
API. However, for this to be able to hook non-dynamic imports, it needs to be
registered before your app code is evaluated via the --import
command-line option.
my-loader.mjs
import * as module from 'module'
module.register('import-in-the-middle/hook.mjs', import.meta.url)
node --import=./my-loader.mjs ./my-code.mjs
When registering the loader hook programmatically, it's possible to pass a list
of modules, file URLs or regular expressions to either exclude
or specifically
include
which modules are intercepted. This is useful if a module is not
compatible with the loader hook.
Note: This feature is incompatible with the {internals: true}
Hook option
import * as module from 'module'
module.register('import-in-the-middle/hook.mjs', import.meta.url, {
data: { exclude: ['package-i-want-to-exclude'] }
})
module.register('import-in-the-middle/hook.mjs', import.meta.url, {
data: { include: ['package-i-want-to-include'] }
})
Only Intercepting Hooked modules
Note: This feature is experimental and is incompatible with the {internals: true}
Hook option
If you are Hook
'ing all modules before they are imported, for example in a
module loaded via the Node.js --import
CLI argument, you can configure the
loader to intercept only modules that were specifically hooked.
instrument.mjs
import { register } from 'module'
import { Hook, createAddHookMessageChannel } from 'import-in-the-middle'
const { registerOptions, waitForAllMessagesAcknowledged } = createAddHookMessageChannel()
register('import-in-the-middle/hook.mjs', import.meta.url, registerOptions)
Hook(['fs'], (exported, name, baseDir) => {
})
await waitForAllMessagesAcknowledged()
my-app.mjs
import * as fs from 'fs'
fs.readFileSync('file.txt')
node --import=./instrument.mjs ./my-app.mjs
Limitations
- You cannot add new exports to a module. You can only modify existing ones.
- While bindings to module exports end up being "re-bound" when modified in a
hook, dynamically imported modules cannot be altered after they're loaded.
- Modules loaded via
require
are not affected at all.